package project.messagestack.network.imap;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import android.content.ContentValues;
import project.messagestack.network.ConnectionHandler;
import project.messagestack.network.MailTransport;
import project.messagestack.network.mail.Message;
import project.messagestack.network.mail.Message.msgType;
import project.messagestack.utils.DatabaseManager;
import project.messagestack.utils.Logger;

public class IMAP implements ConnectionHandler {
  protected MailTransport mConnection;
  protected int msgCount;
  protected int commandCount;
  protected boolean isConnected;
  protected ContentValues mHandlerConfiguration;
  private String hostname;
  protected DatabaseManager mdb;
  private int lastUpdateMsgCount;

  public IMAP(DatabaseManager mdb) {
    this.mdb = mdb;
    commandCount = 0;
    msgCount = -1;
    lastUpdateMsgCount = 0;
  }
  public boolean connect(String hostname, int port, boolean usessl) {
    try {
      mConnection = new MailTransport();
      mConnection.setUri(new URI("imap", hostname, null, null), port);
      mConnection.setSecurity(usessl);
      mConnection.open();
      if (!mConnection.readLine().startsWith("* OK")) {
        return false;
      }
      Logger.logInfo("Connection established to " + hostname);
    } catch (IOException e) {
      e.printStackTrace();
    } catch (URISyntaxException e) {
      e.printStackTrace();
    }
    return true;
  }

  public boolean isConnected() {
    return mConnection.isOpen();
  }

  private void disconnect() {
    if (isConnected()) {
      try {
        mConnection.writeLine("ABCexit LOGOUT");
        mConnection.close();
      } catch (IOException e) {
        Logger.logError("Connection to " + hostname + " can't be closed.");
        e.printStackTrace();
      }
      Logger.logInfo("Connection closed to " + hostname);
    }
  }

  public boolean login(String login, String password) throws IOException {
    if (!isConnected()) {
      Logger.logWarn("Trying to login on not connected socket");
      return false;
    }
    String servResp;

    mConnection.writeLine("ABC" + commandCount + " LOGIN " + login + " "
        + password);
    do {
      servResp = mConnection.readLine();
    } while (!servResp.startsWith("ABC" + commandCount));

    if (servResp.startsWith("ABC" + commandCount + " " + "BAD")) {
      Logger.logError(servResp);
      commandCount++;
      return false;
    } else if (servResp.startsWith("ABC" + commandCount + " " + "NO")) {
      Logger.logError(hostname
          + ": login failure: user name or password rejected");
      commandCount++;
      return false;
    } else if (servResp.startsWith("ABC" + commandCount + " " + "OK")) {
      Logger.logInfo(hostname + ": login successful");
      commandCount++;
      return true;
    }
    return false;
  }

  public boolean selectMailbox(String name) throws IOException {
    if (!isConnected()) {
      Logger.logWarn("Trying to send message on not connected socket");
      return false;
    }
    String servResp;

    mConnection.writeLine("ABC" + commandCount + " SELECT " + name);
    do {
      servResp = mConnection.readLine();
    } while (!servResp.startsWith("ABC" + commandCount));

    if (servResp.startsWith("ABC" + commandCount + " " + "BAD")) {
      Logger.logError(hostname + ": command unknown or arguments invalid");
      commandCount++;
      return false;
    } else if (servResp.startsWith("ABC" + commandCount + " " + "NO")) {
      Logger.logError(hostname
          + ": select failure, now in authenticated state:"
          + " no such mailbox, can't access mailbox");
      commandCount++;
      return false;
    } else if (servResp.startsWith("ABC" + commandCount + " " + "OK")) {
      Logger.logInfo(hostname + ": mailbox selected.");
      commandCount++;
      return true;
    }
    return false;
  }

  public Message readMessage(int numMsg) throws IOException {
    if (numMsg > msgCount && numMsg < 1 && !isConnected()) {
      return null;
    }
    String servResp;
    String content = "";
    String date = "";
    String from = "";
    boolean cont = false;
    mConnection.writeLine("ABC" + commandCount + " FETCH " + numMsg
        + " (BODY[HEADER.FIELDS (DATE FROM )] BODY[TEXT])");
    do {
      servResp = mConnection.readLine();
      if (servResp.startsWith("From:")) {
        from = servResp;
      } else if (servResp.startsWith("Date:")) {
        date = servResp;
      } else if (servResp.startsWith("* " + numMsg + " FETCH")) {
        cont = true;
      } else if (servResp.startsWith(" BODY[HEADER")) {
        cont = false;
      } else if (cont) {
        content += servResp + "\n";
        if(content.length()>400){
          cont=false;
          content+=" (...)";
        }
      }
    } while (!servResp.startsWith("ABC" + commandCount));

    if (servResp.startsWith("ABC" + commandCount + " " + "BAD")) {
      Logger.logError(hostname + ": command unknown or arguments invalid");
      commandCount++;
      return null;
    } else if (servResp.startsWith("ABC" + commandCount + " " + "NO")) {
      Logger.logError(hostname + ": fetch error: can't fetch that data");
      commandCount++;
      return null;
    } else if (servResp.startsWith("ABC" + commandCount + " " + "OK")) {
      Logger.logInfo(hostname + ": fetch completed.");
      content=content.replaceAll("=20", " ");
      content=content.replaceAll("=2E", ".");
      content=content.replaceAll("=2C", ",");
      content=content.replaceAll("=5E", "^");
      content=content.replaceAll("=25", "%");
      content=content.replaceAll("\\+=", "+");
      content=content.replaceAll("=3D", "?");
      from=from.replaceAll("=\\?.*\\?[a-zA-Z0-9]\\?", "");
      from=from.replaceAll("\\?=", "");
      Logger.logDebug(date);
      return new Message("Flag: SEEN" + "\nDate: " + date.substring(6)
          + "\nFrom: " + from.substring(6) + "\nSubject: " + "subject\n\n"
          + content, msgType.MAIL);
    }
    return null;
  }

  public void updateMessageCount(String mailbox) throws IOException {
    if (!isConnected()) {
      Logger.logWarn("Trying to send message on not connected socket");
      return;
    }
    String servResp;
    String completeMsg = "";

    mConnection.writeLine("ABC" + commandCount + " STATUS " + mailbox
        + " (MESSAGES)");
    do {
      servResp = mConnection.readLine();
      completeMsg += servResp;
    } while (!servResp.startsWith("ABC" + commandCount));

    if (servResp.startsWith("ABC" + commandCount + " " + "BAD")) {
      Logger.logError(hostname + ": command unknown or arguments invalid");
      commandCount++;
      msgCount = -1;
      return;
    } else if (servResp.startsWith("ABC" + commandCount + " " + "OK")) {
      String tab[] = completeMsg.split(" ");
      int number = Integer.parseInt(tab[4].substring(0, tab[4].indexOf(")")));
      msgCount = number;
      Logger.logInfo(hostname + ": message count update successful");
      commandCount++;
    }
  }

  public void prepareConnection(ContentValues pList) {
    if (!pList.containsKey("username") || !pList.containsKey("password")
        || !pList.containsKey("hostname") || !pList.containsKey("mailbox")
        || !pList.containsKey("usessl") || !pList.containsKey("port")) {
      return;
    }
    mHandlerConfiguration = pList;
    String username = pList.getAsString("username");
    String password = pList.getAsString("password");
    String hostname = pList.getAsString("hostname");
    boolean usessl = pList.getAsBoolean("usessl");
    this.hostname = hostname;
    int port = pList.getAsInteger("port");
    String mailbox = pList.getAsString("mailbox");
    if (mailbox.equals(null))
      mailbox = "inbox";
    if (connect(hostname, port, usessl)) {
      try {
        login(username, password);
        selectMailbox(mailbox);
        updateMessageCount(mailbox);
      } catch (IOException e) {
        e.printStackTrace();
        return;
      }
    }
  }

  public void updateConfiguration(ContentValues delta) {
    String[] availProps = { "host", "port", "username", "password", "mailbox",
        "usessl" };
    for (int i = 0; i < availProps.length; i++) {
      if (delta.containsKey(availProps[i])) {
        mHandlerConfiguration.remove(availProps[i]);
        mHandlerConfiguration.put(availProps[i],
            delta.getAsString(availProps[i]));
      }
    }
    try {
      disconnect();
      connect(mHandlerConfiguration.getAsString("host"),
          mHandlerConfiguration.getAsInteger("port"),
          mHandlerConfiguration.getAsBoolean("usessl"));
      login(mHandlerConfiguration.getAsString("username"),
          mHandlerConfiguration.getAsString("password"));
      updateMessageCount(mHandlerConfiguration.getAsString("mailbox"));
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void updateData() {
    try {
      this.updateMessageCount("INBOX");
      Message msg = null;
      if (msgCount - lastUpdateMsgCount > 5) {
        lastUpdateMsgCount = msgCount - 5;
      }
      for (; lastUpdateMsgCount < msgCount ; ) {
       msg = readMessage(lastUpdateMsgCount);
       if (msg != null)
        mdb.addNewMessage(msg);
        lastUpdateMsgCount++;
      }
    } catch (IOException e) {
      lastUpdateMsgCount = 0;
      Logger.logError("Error while updating message in IMAP");
    }catch(NullPointerException e1){
      Logger.logError("Error while updating message in IMAP "+lastUpdateMsgCount );
    }
  }

  public void stopConnection() {
    disconnect();
  }

  public boolean isReady() {
    return mConnection.isOpen() && msgCount != -1;
  }

  public int getMsgCount() {
    return msgCount;
  }
  @Override
  public ContentValues getHandlerConfiguration() {
    return mHandlerConfiguration;
  }
}